﻿//==============================================================================
// File: ManagedMarkupExtension.cs
// Created: 2010-06-07
// Author: Piotr Włodek, Arkadiusz Świerczek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with Mammoth. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;

namespace WpfRcp.Core.Common.Localization
{
    /// <summary>
    /// Defines a base class for markup extensions which are managed by a central 
    /// <see cref="MarkupExtensionManager"/>.   This allows the associated markup targets to be
    /// updated via the manager.
    /// </summary>
    /// <remarks>
    /// The ManagedMarkupExtension holds a weak reference to the target object to allow it to update 
    /// the target.  A weak reference is used to avoid a circular dependency which would prevent the
    /// target being garbage collected.  
    /// </remarks>
    public abstract class ManagedMarkupExtension : MarkupExtension
    {
        private static DependencyObject Dummy = new DependencyObject();

        #region Member Variables

        /// <summary>
        /// List of weak reference to the target DependencyObjects to allow them to 
        /// be garbage collected
        /// </summary>
        private readonly List<WeakReference> m_TargetObjects = new List<WeakReference>();

        /// <summary>
        /// The target property 
        /// </summary>
        private object m_TargetProperty;

        #endregion

        #region Public Interface

        /// <summary>
        /// Create a new instance of the markup extension
        /// </summary>
        public ManagedMarkupExtension(MarkupExtensionManager manager)
        {
            manager.RegisterExtension(this);
        }

        /// <summary>
        /// Return the value for this instance of the Markup Extension
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var targetHelper = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (targetHelper != null && targetHelper.TargetObject != null)
            {
                m_TargetProperty = targetHelper.TargetProperty;

                if (targetHelper.TargetObject is DependencyObject || !(m_TargetProperty is DependencyProperty))
                {
                    m_TargetObjects.Add(new WeakReference(targetHelper.TargetObject));
                    return GetValue(targetHelper.TargetObject);
                }
                
                // the extension is being used in a template
                //
                return this;
            }

            return null;
        }

        /// <summary>
        /// Update the associated target
        /// </summary>
        public void UpdateTarget()
        {
            if (m_TargetProperty != null)
            {
                foreach (WeakReference reference in m_TargetObjects)
                {
                    if (m_TargetProperty is DependencyProperty)
                    {
                        var target = reference.Target as FrameworkElement;
                        if (target != null)
                        {
                            target.SetValue(m_TargetProperty as DependencyProperty, GetValue(target));
                        }
                    }
                    else if (m_TargetProperty is PropertyInfo)
                    {
                        var target = reference.Target;
                        if (target != null)
                        {
                            if (target is SetterBase)
                            {
                                if (!(target as SetterBase).IsSealed)
                                {
                                    (m_TargetProperty as PropertyInfo).SetValue(target, GetValue(target), null);
                                }
                            }
                            else
                            {
                                (m_TargetProperty as PropertyInfo).SetValue(target, GetValue(target), null);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Is an associated target still alive ie not garbage collected
        /// </summary>
        public bool IsTargetAlive
        {
            get
            {
                foreach (WeakReference reference in m_TargetObjects)
                {
                    if (reference.IsAlive) return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Gets a value indicating whether we are in the design mode.
        /// </summary>
        public bool IsInDesignMode
        {
            get
            {
                return DesignerProperties.GetIsInDesignMode(Dummy);
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Return the Target Property the extension is associated with
        /// </summary>
        /// <remarks>
        /// Can either be a <see cref="DependencyProperty"/> or <see cref="PropertyInfo"/>
        /// </remarks>
        protected object TargetProperty
        {
            get { return m_TargetProperty as DependencyProperty; }
        }

        /// <summary>
        /// Return the type of the Target Property
        /// </summary>
        protected Type TargetPropertyType
        {
            get
            {
                Type propertyType = null;
                if (m_TargetProperty is DependencyProperty)
                    propertyType = (m_TargetProperty as DependencyProperty).PropertyType;
                else if (m_TargetProperty is PropertyInfo)
                    propertyType = (m_TargetProperty as PropertyInfo).PropertyType;
                return propertyType;
            }
        }

        /// <summary>
        /// Return the value associated with the key from the resource manager
        /// </summary>
        /// <returns>The value from the resources if possible otherwise the default value</returns>
        protected abstract object GetValue(object targetObject);

        #endregion
    }
}